﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.ServiceProcess;
using System.Text;
using System.Threading;

namespace GPSDCTAntaresProtocolListenerWindowsService
{
    // State object for reading client data asynchronously
    public class StateObject
    {
        // Client socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 512;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
    }


    public partial class GPSDCTAntaresProtocolListenerWindowsService : ServiceBase
    {
        public const int BufferSize = 512;

        // Thread signal.
        public static ManualResetEvent allDone = new ManualResetEvent(false);
        public static bool IsThreadAlive = true;


        // Enum Field
        public enum MessageField
        {
            Type,
            EventCode,
            GPSWeek,
            DayOfWeek,
            GPSTimeOfDay,
            Latitude,
            Longitude,
            Speed,
            Heading,
            Source,
            AgeOfData
        }

        public GPSDCTAntaresProtocolListenerWindowsService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            try
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("--- GPS Tracking Server Start Listening GPS Antares DCT V1 ---", "GPS Antares DCT");
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("IP: " + System.Net.Dns.GetHostName(), "GPS Antares DCT");
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Port: " + Properties.Settings.Default.Port.ToString(), "GPS Antares DCT");
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Max Connections: " + Properties.Settings.Default.MaxConnections.ToString(), "GPS Antares DCT");

                IsThreadAlive = true;
                Thread TCPListenerThread = new Thread(StartListening);
                TCPListenerThread.Start();
            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error..... Message:" + ex.Message + " StackTrace:" + ex.StackTrace, "GPS Antares DCT");
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("GPS Antares DCT Server is DOWN", "GPS Antares DCT");
                this.Stop();
            }
        }

        protected override void OnStop()
        {
            IsThreadAlive = false;
            GPSTrackingDBCustom.Log.WindowsServiceLog.Warning("--- GPS Tracking Server GPS Antares DCT Is DOWN ---", "GPS Antares DCT");            
        }

        public static void StartListening()
        {
            IPEndPoint ep = new IPEndPoint(IPAddress.Any, Properties.Settings.Default.Port);

            // Create a TCP/IP socket for listner.
            Socket listenerSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Bind the socket to the endpoint and wait for listen for incoming connections.
            try
            {
                listenerSock.Bind(ep);
                listenerSock.Listen(Properties.Settings.Default.MaxConnections);

                while (IsThreadAlive)
                {
                    try
                    {
                        // Set the event to nonsignaled state.
                        allDone.Reset();

                        // Start an asynchronous socket to listen for connections.
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Waiting for a connection GPS Antares DCT.....", "GPS Antares DCT");
                        listenerSock.BeginAccept(new AsyncCallback(AcceptCallback), listenerSock);

                        // Wait until a connection is made before continuing.
                        allDone.WaitOne();
                    }
                    catch (Exception ex)
                    {
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error Waiting for a connection..... Message:" + ex.Message + " StackTrace:" + ex.StackTrace, "GPS Antares DCT");
                        listenerSock.Shutdown(SocketShutdown.Both);
                        listenerSock.Close();                        
                    }

                }

            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error..... Message:" + ex.Message + " StackTrace:" + ex.StackTrace, "GPS Antares DCT");
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("GPS Antares DCT Server is DOWN", "GPS Antares DCT");                
            }
        }

        public static void AcceptCallback(IAsyncResult ar)
        {
            try
            {
                // Signal the main thread to continue.
                allDone.Set();

                // Get the socket that handles the client request.
                Socket listener = (Socket)ar.AsyncState;
                Socket handler = listener.EndAccept(ar);

                // Create the state object.
                StateObject MessageDevice = new StateObject();
                MessageDevice.workSocket = handler;
                handler.BeginReceive(MessageDevice.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), MessageDevice);
            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error..... Message:" + ex.Message + " StackTrace:" + ex.StackTrace, "GPS Antares DCT");
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("GPS Antares DCT Server is DOWN", "GPS Antares DCT");
            }
        }


        public static int GetIndexOfMessageOf(MessageField messageField)
        {
            int index = 0;

            if (MessageField.Type == messageField) return index; else index += 3;
            if (MessageField.EventCode == messageField) return index; else index += 2;
            if (MessageField.GPSWeek == messageField) return index; else index += 4;
            if (MessageField.DayOfWeek == messageField) return index; else index += 1;
            if (MessageField.GPSTimeOfDay == messageField) return index; else index += 5;
            if (MessageField.Latitude == messageField) return index; else index += 8;
            if (MessageField.Longitude == messageField) return index; else index += 9;
            if (MessageField.Speed == messageField) return index; else index += 3;
            if (MessageField.Heading == messageField) return index; else index += 3;
            if (MessageField.Source == messageField) return index; else index += 1;
            if (MessageField.AgeOfData == messageField) return index; else index += 1;

            return index++;            
        }


        public static void ReadCallback(IAsyncResult ar)
        {
            int VarSeg = 0;
            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject MessageDevice = (StateObject)ar.AsyncState;
            Socket handler = MessageDevice.workSocket;

            // Read data from the client socket.
            int bytesRead = handler.EndReceive(ar);

            try
            {
                //No Message 
                if (bytesRead <= 0)
                {
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Disconnected... gps device. No Message", "GPS Antares DCT");
                    handler.Shutdown(SocketShutdown.Both);
                    handler.Close();

                    return;
                }

                //Process Message GPS Antares
                VarSeg = 1;
                string Message = System.Text.UTF8Encoding.UTF8.GetString(MessageDevice.buffer).Replace("\0", "");                
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Message Device: " + Message, "GPS Antares DCT");
                Message = Message.Substring(1, 59); //Fix Line                

                //Latitudes
                VarSeg = 2;
                int index = GetIndexOfMessageOf(MessageField.Latitude);                
                string DeviceLatitude = Message.Substring(index+1, 7); //8 latitudes
                
                //Longitude
                VarSeg = 3;
                index = GetIndexOfMessageOf(MessageField.Longitude);                
                string DeviceLongitude = Message.Substring(index+1, 8); //9 Latitudes

                //Speed
                VarSeg = 4;
                index = GetIndexOfMessageOf(MessageField.Speed);
                string DeviceSpeed = Message.Substring(index, 3); // 3 Speed

                //Heading
                VarSeg = 5;
                index = GetIndexOfMessageOf(MessageField.Heading);
                string DeviceHeading = Message.Substring(index, 3); // 3 Heading

                //Imei
                VarSeg = 6;
                string DeviceID = Message.Substring(Message.Length - 15);
                
                double Speed = Convert.ToDouble(DeviceSpeed) * 1.852; //Converto knots to KM   1KM = 1.852
                double Heading = Convert.ToDouble(DeviceHeading);

                //10.06261, -084.43766
                VarSeg = 7;
                double Latitude = Convert.ToDouble(DeviceLatitude.Insert(2, ","));
                double Longitude = Convert.ToDouble(DeviceLongitude.Insert(3, ","));
                Longitude = Longitude * -1;

                
                //Insert to Database
                VarSeg = 8;
                GPSTrackingDBV2.Entities.TrackData TrackDataEntity = new GPSTrackingDBV2.Entities.TrackData();
                try
                {
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Inserting track message to database ...", "GPS Antares DCT");
                    GPSTrackingDBV2.Entities.TrackDataLastLocation TrackDataLastLocationEntity = new GPSTrackingDBV2.Entities.TrackDataLastLocation();                    

                    TrackDataLastLocationEntity.GpsImei = TrackDataEntity.GpsImei = DeviceID;
                    TrackDataLastLocationEntity.DateTime = TrackDataEntity.DateTime = DateTime.Now;
                    TrackDataLastLocationEntity.Latitude = TrackDataEntity.Latitude = Latitude;
                    TrackDataLastLocationEntity.Longitude = TrackDataEntity.Longitude = Longitude;
                    TrackDataLastLocationEntity.Heading =  TrackDataEntity.Heading = Heading;
                    TrackDataLastLocationEntity.Speed = TrackDataEntity.Speed = Speed;

                    GPSTrackingDBV2.Data.DataRepository.TrackDataProvider.Insert(TrackDataEntity);

                    //Update last location  
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Updating last track message location to database ...", "GPS Antares DCT");
                    try
                    {
                        GPSTrackingDBV2Custom.Data.DataRepository.TrackDataLastLocationProvider.UpdateOrInsert(TrackDataLastLocationEntity);
                    }
                    catch (Exception ex)
                    {
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error Updating last track message:" + ex.Message, "GPS Antares DCT");
                        // Disconnect 
                        handler.Shutdown(SocketShutdown.Both);
                        handler.Close();           
                        
                    }


                }
                catch (Exception ex)
                {
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error Inserting track message:" + ex.Message, "GPS Antares DCT");
                    // Disconnect 
                    handler.Shutdown(SocketShutdown.Both);
                    handler.Close();
                }

                // Disconnect 
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Disconnected... gps device.", "GPS Antares DCT");
                //Send(handler, Encoding.ASCII.GetBytes("OK"));

            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error message invalid: Line:" + VarSeg.ToString() + ' ' + ex.Message + ", Disconnected... gps device.", "GPS Antares DCT");
                // Disconnect 
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();                
            }

        }
    }
}
